/*
*
*    This program is free software; you can redistribute it and/or modify it
*    under the terms of the GNU General Public License as published by the
*    Free Software Foundation; either version 2 of the License, or (at
*    your option) any later version.
*
*    This program is distributed in the hope that it will be useful, but
*    WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software Foundation,
*    Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*    In addition, as a special exception, the author gives permission to
*    link the code of this program with the Half-Life Game Engine ("HL
*    Engine") and Modified Game Libraries ("MODs") developed by Valve,
*    L.L.C ("Valve").  You must obey the GNU General Public License in all
*    respects for all of the code used other than the HL Engine and MODs
*    from Valve.  If you modify this file, you may extend this exception
*    to your version of the file, but you are not obligated to do so.  If
*    you do not wish to do so, delete this exception statement from your
*    version.
*
*/

#include "precompiled.h"

#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>

// Constructors
CUtlBuffer::CUtlBuffer(int growSize, int initSize, bool text) :
	m_Memory(growSize, initSize),
	m_Error(0)
{
	m_Get = 0;
	m_Put = 0;
	m_Flags = 0;
	if (text)
	{
		m_Flags |= TEXT_BUFFER;
	}
}

CUtlBuffer::CUtlBuffer(void const *pBuffer, int size, bool text) :
	m_Memory((unsigned char *)pBuffer, size),
	m_Error(0)
{
	m_Get = 0;
	m_Put = 0;
	m_Flags = 0;

	if (text)
	{
		m_Flags |= TEXT_BUFFER;
	}
}

// Attaches the buffer to external memory....
void CUtlBuffer::SetExternalBuffer(void *pMemory, int numElements, bool text)
{
	m_Memory.SetExternalBuffer((unsigned char *)pMemory, numElements);

	// Reset all indices; we just changed memory
	m_Get = 0;
	m_Put = 0;
	m_Flags = 0;

	if (text)
	{
		m_Flags |= TEXT_BUFFER;
	}
}

// Makes sure we've got at least this much memory
void CUtlBuffer::EnsureCapacity(int num)
{
	m_Memory.EnsureCapacity(num);
}

// Base get method from which all others derive
void CUtlBuffer::Get(void *pMem, int size)
{
	Assert(m_Get + size <= m_Memory.NumAllocated());
	memcpy(pMem, &m_Memory[m_Get], size);
	m_Get += size;
}

// Eats whitespace
void CUtlBuffer::EatWhiteSpace()
{
	if (IsText() && IsValid())
	{
		int lastpos = Size();
		while (m_Get < lastpos)
		{
			if (!isspace(*(char *)&m_Memory[m_Get]))
				break;

			m_Get += sizeof(char);
		}
	}
}

// Reads a null-terminated string
void CUtlBuffer::GetString(char *pString, int nMaxLen)
{
	if (!IsValid())
	{
		*pString = '\0';
		return;
	}

	if (nMaxLen == 0)
	{
		nMaxLen = INT_MAX;
	}

	if (!IsText())
	{
		int len = strlen((char *)&m_Memory[m_Get]) + 1;
		if (len <= nMaxLen)
		{
			Get(pString, len);
		}
		else
		{
			Get(pString, nMaxLen);
			pString[nMaxLen - 1] = '\0';
			SeekGet(SEEK_CURRENT, len - nMaxLen);
		}
	}
	else
	{
		// eat all whitespace
		EatWhiteSpace();

		// Eat characters
		int nCount = 0;
		int nLastPos = Size();
		while (m_Get < nLastPos)
		{
			char c = *(char *)&m_Memory[m_Get];
			if (isspace(c) || (!c))
				break;

			if (nCount < nMaxLen - 1)
			{
				*pString++ = c;
			}

			nCount++;
			m_Get++;
		}

		// Terminate
		*pString = '\0';
	}
}

// Checks if a get is ok
bool CUtlBuffer::CheckGet(int size)
{
	if (m_Error)
		return false;

	if (m_Memory.NumAllocated() >= m_Get + size)
		return true;

	m_Error |= GET_OVERFLOW;
	return false;
}

// Change where I'm reading
void CUtlBuffer::SeekGet(SeekType_t type, int offset)
{
	switch (type)
	{
	case SEEK_HEAD:
		m_Get = offset;
		break;

	case SEEK_CURRENT:
		m_Get += offset;
		break;

	case SEEK_TAIL:
		m_Get = m_Memory.NumAllocated() - offset;
		break;
	}
}

// Parse...
#pragma warning(disable:4706)

int CUtlBuffer::VaScanf(char const *pFmt, va_list list)
{
	Assert(pFmt);
	if (m_Error || !IsText())
		return 0;

	int numScanned = 0;

	char c;
	char *pEnd;
	while (c = *pFmt++)
	{
		// Stop if we hit the end of the buffer
		if (m_Get >= Size())
		{
			m_Error |= GET_OVERFLOW;
			break;
		}

		switch (c)
		{
		case ' ':
			// eat all whitespace
			EatWhiteSpace();
			break;

		case '%':
		{
			// Conversion character... try to convert baby!
			char type = *pFmt++;
			if (type == 0)
				return numScanned;

			switch (type)
			{
			case 'c':
			{
				char *ch = va_arg(list, char *);
				*ch = (char)m_Memory[m_Get];
				m_Get++;
			}
				break;

			case 'i':
			case 'd':
			{
				int *i = va_arg(list, int *);
				*i = strtol((char *)PeekGet(), &pEnd, 10);
				if (pEnd == PeekGet())
					return numScanned;
				m_Get = (int)pEnd - (int)Base();
			}
				break;

			case 'x':
			{
				int *i = va_arg(list, int *);
				*i = strtol((char *)PeekGet(), &pEnd, 16);
				if (pEnd == PeekGet())
					return numScanned;
				m_Get = (int)pEnd - (int)Base();
			}
				break;

			case 'u':
			{
				unsigned int *u = va_arg(list, unsigned int *);
				*u = strtoul((char *)PeekGet(), &pEnd, 10);
				if (pEnd == PeekGet())
					return numScanned;
				m_Get = (int)pEnd - (int)Base();
			}
				break;

			case 'f':
			{
				float *f = va_arg(list, float *);
				*f = (float)strtod((char *)PeekGet(), &pEnd);
				if (pEnd == PeekGet())
					return numScanned;
				m_Get = (int)pEnd - (int)Base();
			}
				break;

			case 's':
			{
				char *s = va_arg(list, char *);
				GetString(s);
			}
				break;

			default:
			{
				// unimplemented scanf type
				Assert(0);
				return numScanned;
			}
				break;
			}

			numScanned++;
		}
			break;

		default:
		{
			// Here we have to match the format string character
			// against what's in the buffer or we're done.
			if (c != m_Memory[m_Get])
				return numScanned;
			m_Get++;
		}
		}
	}

	return numScanned;
}

#pragma warning(default:4706)

int CUtlBuffer::Scanf(char const *pFmt, ...)
{
	va_list args;

	va_start(args, pFmt);
	int count = VaScanf(pFmt, args);
	va_end(args);

	return count;
}

// Serialization
void CUtlBuffer::Put(void const *pMem, int size)
{
	if (CheckPut(size))
	{
		memcpy(&m_Memory[m_Put], pMem, size);
		m_Put += size;
	}
}

// Writes a null-terminated string
void CUtlBuffer::PutString(char const *pString)
{
	int len = strlen(pString);

	// Not text? append a null at the end.
	if (!IsText())
		len++;

	Put(pString, len);
}

void CUtlBuffer::VaPrintf(char const *pFmt, va_list list)
{
	char temp[2048];
	int len = vsprintf(temp, pFmt, list);
	Assert(len < 2048);

	// Not text? append a null at the end.
	if (!IsText())
		len++;

	Put(temp, len);
}

void CUtlBuffer::Printf(char const *pFmt, ...)
{
	va_list args;

	va_start(args, pFmt);
	VaPrintf(pFmt, args);
	va_end(args);
}

// Checks if a put is ok
bool CUtlBuffer::CheckPut(int size)
{
	if (m_Error)
		return false;

	while (m_Memory.NumAllocated() < m_Put + size)
	{
		if (m_Memory.IsExternallyAllocated())
		{
			m_Error |= PUT_OVERFLOW;
			return false;
		}

		m_Memory.Grow();
	}

	return true;
}

void CUtlBuffer::SeekPut(SeekType_t type, int offset)
{
	switch (type)
	{
	case SEEK_HEAD:
		m_Put = offset;
		break;

	case SEEK_CURRENT:
		m_Put += offset;
		break;

	case SEEK_TAIL:
		m_Put = m_Memory.NumAllocated() - offset;
		break;
	}
}
